--- name: html-to-images description: Export a single-file HTML slide deck as individual PNG images — one per slide. Renders in headless Chrome with fullPage screenshot, then slices with Pillow. Use when the user wants slide images for social media, docs, or any use case that doesn't need PPTX. --- # HTML to Images Skill Export any single-file HTML slide deck as individual PNG images. Each slide becomes its own file: `slide-01.png`, `slide-02.png`, etc. ## How It Works 1. **Headless Chrome** renders the full HTML page at once (`fullPage: true`) → one tall PNG 2. **Pillow** slices it into N equal strips and saves each as a numbered PNG No PPTX, no python-pptx. Just clean image files ready for anywhere. --- ## Requirements | Tool | Install | |---|---| | Node.js | https://nodejs.org | | Python 3 | https://python.org | | Puppeteer | `npm install puppeteer` (auto-handled) | | Pillow | `pip install pillow` | | Chrome or Edge | Already installed on most systems | --- ## Step-by-Step Instructions ### 1. Ask the user for inputs You need: - **HTML file path** — the source presentation - **Number of slides** — how many slides the deck has - **Output folder** — where to save the images (default: a `slides/` subfolder next to the HTML) - **Output format** — PNG (default) or JPEG - **Resolution** — default 1920×1080. Ask only if they want something different (e.g. 2560×1440 for retina, 1080×1080 for square social). Auto-detect Chrome from these paths (check in order): - `C:/Program Files/Google/Chrome/Application/chrome.exe` - `C:/Program Files (x86)/Google/Chrome/Application/chrome.exe` - `C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe` - `/usr/bin/google-chrome` - `/usr/bin/chromium-browser` ### 2. Install puppeteer if needed ```bash node -e "require('puppeteer')" 2>&1 npm install puppeteer --save-dev ``` ### 3. Write the screenshot script Write `_screenshot.mjs` to the same folder as the HTML: ```js import puppeteer from 'puppeteer'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const HTML_PATH = path.join(__dirname, ''); const W = ; // e.g. 1920 const H = ; // e.g. 1080 const browser = await puppeteer.launch({ executablePath: '', headless: true, args: ['--no-sandbox', `--window-size=${W},${H}`], }); const page = await browser.newPage(); await page.setViewport({ width: W, height: H, deviceScaleFactor: 1 }); await page.goto(`file:///${HTML_PATH.replace(/\\/g, '/')}`, { waitUntil: 'networkidle0', timeout: 30000, }); // Wait for fonts and transitions to settle await new Promise(r => setTimeout(r, 2500)); // Freeze animations, hide fixed UI chrome await page.addStyleTag({ content: ` * { animation: none !important; transition: none !important; } body::after { display: none !important; } #nav, #progress, nav, header[data-fixed] { display: none !important; } ` }); await page.screenshot({ path: path.join(__dirname, '_fullpage.png'), fullPage: true, }); console.log('Full page captured.'); await browser.close(); ``` ### 4. Write the slice script Write `_slice_images.py` to the same folder: ```python from PIL import Image import os FOLDER = r'' FULLPAGE = os.path.join(FOLDER, '_fullpage.png') OUT_DIR = os.path.join(FOLDER, '') # e.g. 'slides' SLIDES = FORMAT = '' # 'PNG' or 'JPEG' EXT = 'jpg' if FORMAT == 'JPEG' else 'png' os.makedirs(OUT_DIR, exist_ok=True) img = Image.open(FULLPAGE) W, H = img.size slide_h = H // SLIDES print(f"Full image: {W}x{H}, {SLIDES} slides, {slide_h}px each") print(f"Saving to: {OUT_DIR}") for i in range(SLIDES): top = i * slide_h crop = img.crop((0, top, W, top + slide_h)) out_path = os.path.join(OUT_DIR, f'slide-{i+1:02d}.{EXT}') if FORMAT == 'JPEG': crop = crop.convert('RGB') # JPEG does not support transparency crop.save(out_path, format=FORMAT, quality=95) else: crop.save(out_path, format=FORMAT) print(f" Saved slide-{i+1:02d}.{EXT} (rows {top}-{top+slide_h})") print(f"\nDone. {SLIDES} images saved to {OUT_DIR}/") ``` ### 5. Run both scripts ```bash node _screenshot.mjs python _slice_images.py ``` ### 6. Clean up temp files After confirming the images look correct, delete: - `_fullpage.png` - `_screenshot.mjs` - `_slice_images.py` The output images in the `slides/` folder are kept. --- ## Output ``` slides/ ├── slide-01.png ├── slide-02.png ├── slide-03.png ... └── slide-10.png ``` --- ## Common Issues | Problem | Cause | Fix | |---|---|---| | All images identical | Using scroll + viewport clip | Always use `fullPage: true` + slice — never scroll | | Slide count wrong | Slide height inconsistency in HTML | Ensure every slide is exactly `100vh` | | JPEG has black background | Transparency not supported by JPEG | Skill auto-converts to RGB before saving | | Chrome not found | Wrong exe path | Check Edge as fallback |